package ge;

import ge.standardsymbols.ConstantSymbol;
import ge.standardsymbols.StartSymbol;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.codehaus.janino.ClassBodyEvaluator;
import org.codehaus.janino.CompileException;
import org.codehaus.janino.Scanner;
import org.codehaus.janino.Parser.ParseException;
import org.codehaus.janino.Scanner.ScanException;

public class BNFGrammarParser {
	private static final Logger LOG = Logger.getLogger(BNFGrammarParser.class);
	private ClassLoader _class_loader = null;

	public List<ProductionRule> rules = null;
	private String _symbol_package = null;


	public BNFGrammarParser(String grammar, String symbol_package) {
		_class_loader = new SymbolClassLoader();
		String[] raw_rules = grammar.split("\n\n");
		_symbol_package = symbol_package;

		rules = new ArrayList<ProductionRule>(raw_rules.length);

		for (String raw_rule : raw_rules) {
			rules.add(createProductionRule(raw_rule));
		}
	}

	public BNFGrammarParser(File file, String symbol_package) {
		_class_loader = new SymbolClassLoader();
		StringBuilder str = new StringBuilder();
		try {
			BufferedReader reader = new BufferedReader(new FileReader(file));
			String line = null;
			while ((line = reader.readLine()) != null) {
				str.append(line);
				str.append('\n');
			}
			reader.close();
			reader = null;
		} catch (Exception e) {
			LOG.fatal("Failed to read in grammar file:" + file, e);
			System.exit(0);
		}

		String[] raw_rules = str.toString().split("\n\\s*\n");
		_symbol_package = symbol_package;

		rules = new ArrayList<ProductionRule>(raw_rules.length);

		for (String raw_rule : raw_rules) {
			if (!raw_rule.startsWith("#")) {
			rules.add(createProductionRule(raw_rule));
			}
		}
	}

	@SuppressWarnings("unchecked")
	private ProductionRule createProductionRule(String raw_rule) {
		String[] alternatives = raw_rule.split("\n+\r*");

		String[] head_tail = alternatives[0].split("::=");

		// get the head of the statement extracting the nonterminal
		Class<? extends NonTerminal> nonterminal = null;

		// remove angle brackets
		head_tail[0] = head_tail[0].trim();
		head_tail[0] = head_tail[0].substring(1, head_tail[0].length() - 1);

		// StartSymbol is a special symbol so we won't try to load it from the
		// defined package
		if (head_tail[0].startsWith("StartSymbol")) {
			nonterminal = StartSymbol.class;
		} else {
			try {
				// Try and load the symbol from the given package
				nonterminal = (Class<? extends NonTerminal>) _class_loader
						.loadClass(_symbol_package + head_tail[0]);
			} catch (ClassNotFoundException e) {
				LOG.fatal("Failed to find class: " + head_tail[0]);
				System.exit(0);
			}
		}

		// Construct the new production rule using the parsed and loaded
		// nonterminal
		ProductionRule rule = new ProductionRule(nonterminal);

		alternatives[0] = head_tail[1];

		// for each possible replacement expression
		for (String expression : alternatives) {
			expression = expression.trim();

			// remove the vertical bar prefacing the alternatives
			if (expression.startsWith("|")) {
				expression = expression.substring(1).trim();
			}

			String[] raw_symbols = expression.split(" ");
			Object[] symbols = new Object[raw_symbols.length];
			for (int i = 0; i < raw_symbols.length; i++) {
				// remove any white space
				String raw_symbol = raw_symbols[i].trim();

				if (raw_symbol.startsWith("<") && raw_symbol.endsWith(">")) {
					// remove angle brackets
					raw_symbol = raw_symbol.substring(1,
							raw_symbol.length() - 1);
					try {
						symbols[i] = _class_loader.loadClass(_symbol_package
								+ raw_symbol);
					} catch (ClassNotFoundException e) {
						LOG.fatal("Can't find class: " + raw_symbol);
						System.exit(0);
					}
				} else if (raw_symbol.startsWith("\"")
						&& raw_symbol.endsWith("\"")) {
					// remove quotes
					raw_symbol = raw_symbol.substring(1,
							raw_symbol.length() - 1);
					raw_symbol = raw_symbol.replace('_', ' ');
					raw_symbol = raw_symbol.replace("\\n", "\n");
					symbols[i] = new ConstantSymbol(raw_symbol);
				}

			}

			rule.add(new Expression(symbols));
		}

		return rule;
	}

	public String toString() {
		StringBuilder str = new StringBuilder();
		for (ProductionRule rule : rules) {
			str.append(rule);
			str.append("Minimum Termination Depth=");
			str.append(rule.getMinimumTerminationDepth());
			str.append('\n');
			str.append('\n');
		}
		return str.toString();
	}
}
